/**
 *    Copyright 2009-2020 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.reflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

public class ParamNameResolver {

  public static final String GENERIC_NAME_PREFIX = "param";

  private final boolean useActualParamName;

  /**
   * <p>
   * The key is the index and the value is the name of the parameter.<br />
   * The name is obtained from {@link Param} if specified. When {@link Param} is not specified,
   * the parameter index is used. Note that this index could be different from the actual index
   * when the method has special parameters (i.e. {@link RowBounds} or {@link ResultHandler}).
   * </p>
   * <ul>
   * <li>aMethod(@Param("M") int a, @Param("N") int b) -&gt; {{0, "M"}, {1, "N"}}</li>
   * <li>aMethod(int a, int b) -&gt; {{0, "0"}, {1, "1"}}</li>
   * <li>aMethod(int a, RowBounds rb, int b) -&gt; {{0, "0"}, {2, "1"}}</li>
   * </ul>
   */
  /*xxx: 使用该字段，记录了 参数在参数列表中的位置索引，与参数名称的对应关系，
     其中，key 表示 索引位置，value表示参数名称,*/
  /*xxx: 参数名称 可通过  @Param指定，如未指定，则用  参数索引  作为名称*/

  /*xxx: 如参数列表中包含 RowBounds类型 ，或 ResultHandler 类型的参数，则这两种类型参数 不会被记录到 names集合中*/
  private final SortedMap<Integer, String> names;

  /*xxx: 记录对应方法的参数列表 是否使用了  @Param 注解*/
  private boolean hasParamAnnotation;

  public ParamNameResolver(Configuration config, Method method) {
    /*xxx: 是否用实际的参数名作为 索引，默认为 true*/
    this.useActualParamName = config.isUseActualParamName();
    /*xxx: 获取参数列表中 ，每个参数的类型*/
    final Class<?>[] paramTypes = method.getParameterTypes();
    /*xxx: 获取参数列表上的注解,二维数组，数组的维度与参数个数相同*/
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    /*xxx: 该集合，用于记录参数索引  与 参数名称的对应关系  */
    final SortedMap<Integer, String> map = new TreeMap<>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    /*xxx: 遍历方法所有参数*/
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        /*xxx: 如果参数是 RowBound类型，或 ResultHandler类型，则跳过对该参数的分析*/
        continue;
      }
      String name = null;
      /*xxx: 遍历该参数对应的注解集合*/
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          /*xxx: @Param注解出现过一次，就将 hasParamAnnotation初始化为 true*/
          hasParamAnnotation = true;
          /*xxx: 获取 @Param 注解指定的参数名称*/
          name = ((Param) annotation).value();
          break;
        }
      }
      /*xxx: 如果没有指定  @Param*/
      if (name == null) {
        // @Param was not specified.
        /*xxx: 默认情况下，使用实际的参数名 作为索引名称*/
        if (useActualParamName) {
          name = getActualParamName(method, paramIndex);
        }
        /*xxx: 如果，配置了不使用实际的参数名  作为 索引名*/
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          /*xxx: 则使用 参数的索引 作为 名称*/
          name = String.valueOf(map.size());
        }
      }
      /*xxx: 记录到 map 中保存*/
      map.put(paramIndex, name);
    }
    /*xxx: 初始化  names 集合  */
    names = Collections.unmodifiableSortedMap(map);
  }

  private String getActualParamName(Method method, int paramIndex) {
    /*xxx: 获取某个方法的  指定索引位置的 方法名称*/
    return ParamNameUtil.getParamNames(method).get(paramIndex);
  }

  /*xxx: RowBounds 与 ResultHandler 视为 特殊参数*/
  private static boolean isSpecialParameter(Class<?> clazz) {
    return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
  }

  /**
   * Returns parameter names referenced by SQL providers.
   *
   * @return the names
   */
  public String[] getNames() {
    return names.values().toArray(new String[0]);
  }

  /**
   * <p>
   * A single non-special parameter is returned without a name.
   * Multiple parameters are named using the naming rule.
   * In addition to the default names, this method also adds the generic names (param1, param2,
   * ...).
   * </p>
   *
   * @param args
   *          the args
   * @return the named params
   */
  /*xxx: 该方法接收的参数 是 用户传入的 实参列表,*/
  public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    /*xxx: 无参数，返回 null*/
    if (args == null || paramCount == 0) {
      return null;
      /*xxx: 未使用 @Param 注解，且只有 一个参数时*/
    } else if (!hasParamAnnotation && paramCount == 1) {
      Object value = args[names.firstKey()];
      /*xxx: 对实参为 复合参数 的进行包装*/
      return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
    } else {
      /*xxx: 这个map 记录 参数名称 与 实参之间的 对应关系*/
      /*xxx: 如果 向 ParamMap添加已经存在的 key，会报错*/
      /*xxx: 即可以通过两种方式获取参数，param+索引，或者 实际的索引获取*/
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        /*xxx： 将参数名  与 实参对应关系 记录到  param中*/
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        /*xxx: 为参数创建 param+索引  格式的  默认参数名称,并添加到param集合中 */
        final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
        // ensure not to overwrite parameter named with @Param
        /*xxx: 如果 @Param 指定的参数名称 就是  "param+索引" 格式的，则不需要再添加*/
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

  /**
   * Wrap to a {@link ParamMap} if object is {@link Collection} or array.
   *
   * @param object a parameter object
   * @param actualParamName an actual parameter name
   *                        (If specify a name, set an object to {@link ParamMap} with specified name)
   * @return a {@link ParamMap}
   * @since 3.5.5
   */
  /*xxx: 将集合包装为 Map字典*/
  public static Object wrapToMapIfCollection(Object object, String actualParamName) {
    /*xxx: 若对象为 Collection类型，会将参数包装为
       {
          参数:{list:[],collection:[]}
       }
       形式*/
    if (object instanceof Collection) {
      ParamMap<Object> map = new ParamMap<>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
      return map;
      /*xxx: 如果 对象  为 数组,则返回
         {
            参数:{array: []}
         }形式*/
    } else if (object != null && object.getClass().isArray()) {
      ParamMap<Object> map = new ParamMap<>();
      map.put("array", object);
      Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
      return map;
    }
    return object;
  }

}
